#ifndef MATRIX_H
#define MATRIX_H

#include <math.h>
#include <iostream>
using namespace std;

template<typename T>
class Matrix
{
public:
	// Constructors
	Matrix();
	Matrix(const int row, const int col);
	Matrix(const Matrix<T> &m);

	// Destructor
	~Matrix();

	// Subscript operator
	T& operator () (const int row, const int col);

	// Assignment operator
	const Matrix<T>& operator = (const Matrix<T> &m);

	// Logical operator
	bool operator == (const Matrix<T> &m);
	bool operator != (const Matrix<T> &m);

	// Calculation operators
	Matrix<T> operator + (const Matrix<T> &m);
	Matrix<T> operator - (const Matrix<T> &m);
	Matrix<T> operator * (const Matrix<T> &m);
	Matrix<T> operator * (const T &num);
	friend Matrix<T> operator * (const T &num, const Matrix<T> &m){return (m*num);}
	Matrix<T> operator / (const T &num);

	// IO-stream operators
	template<typename U>
	friend istream & operator >>  (istream & input, Matrix<U> &m);
	template<typename U>
	friend ostream & operator <<  (ostream & output, const Matrix<U> &m);

	// Get function
	int getRowSize();
	int getColSize();

private:
	T **element;
	int _rowSize, _colSize;

	// Reallocation method
	void reallocate(int row, int col);
};

// Input stream function
template<typename T>
istream & operator >> (istream & input, Matrix<T> & m)
{
   for (int i=0; i < m._rowSize; i++)
      for (int j=0; j < m._colSize; j++)
         input >> m.element[i][j];

   return input;
}

// Output stream function
template<typename T>
ostream & operator << (ostream & output, const Matrix<T> & m)
{
	for (int i=0; i < m._rowSize; i++){
		for (int j=0; j < m._colSize; j++)
			output << m.element[i][j] << " ";
		output << endl;
	}
	output<<endl;

	return output;
}

template<typename T>
Matrix<T>::Matrix()
{
	_rowSize = 1;
	_colSize = 1;
	element = new T* [_rowSize];
	for (int i=0; i<_rowSize; i++)
		element[i] = new T [_colSize];
}

// Constructor
template<typename T>
Matrix<T>::Matrix(const int row, const int col)
{
	_rowSize = row;
	_colSize = col;
	element = new T* [_rowSize];
	for (int i=0; i<_rowSize; i++)
		element[i] = new T [_colSize];
}

// Copy constructor
template<typename T>
Matrix<T>::Matrix(const Matrix<T> &m)
{
	_rowSize = m._rowSize;
	_colSize = m._colSize;

	element = new T* [_rowSize];

	for (int i=0; i<_rowSize; i++)
	{
		element[i] = new T [_colSize];
		for (int j=0; j<_colSize; j++)
		{
			element[i][j] = m.element[i][j];
		}
	}
}

// Destructor
template<typename T>
Matrix<T>::~Matrix()
{
	for (int i=0; i<_rowSize; i++)
		delete [] element[i];
	delete [] element;
}

// Reallocation method
template<typename T>
void Matrix<T>::reallocate(int row, int col)
{
	if (row == _rowSize && col == _colSize)
		return;

	T** temp = new T* [row];
	for (int i=0; i<row; i++)
		temp[i] = new T [col];

	int minRow = row < _rowSize ? row : _rowSize;
	int minCol = col < _colSize ? col : _colSize;

	for (int i=0; i<minRow; i++)
		for (int j=0; j<minCol; j++)
			temp[i][j] = element[i][j];

	for (int i=0; i<_rowSize; i++)
		delete [] element[i];
	delete [] element;

	_rowSize = row;
	_colSize = col;
	element = temp;

	return;
}

// Subscript operator
template<typename T>
T& Matrix<T>::operator ()(const int row, const int col)
{
	if (row < _rowSize && col < _colSize)
		return element[row][col];
}

// Assignment operator
template<typename T>
const Matrix<T>& Matrix<T>::operator =(const Matrix<T> &m)
{
	if (_rowSize != m._rowSize || _colSize != m._colSize)
		reallocate(m._rowSize, m._colSize);

	for (int i=0; i<_rowSize; i++)
		for (int j=0; j<_colSize; j++)
			element[i][j] = m.element[i][j];

	return *this;
}

// Logical operator: euqal to
template<typename T>
bool Matrix<T>::operator == (const Matrix<T> &m)
{
	if (_rowSize != m._rowSize || _colSize != m._colSize)
		return false;

	for (int i=0; i<_rowSize; i++)
		for (int j=0; j<_colSize; j++)
			if (element[i][j] != m.element[i][j])
				return false;

	return true;
}

// Logical operator: not euqal to
template<typename T>
bool Matrix<T>::operator != (const Matrix<T> &m)
{
	return (*this == m) ? false : true;
}

// Binary addition operator
template<typename T>
Matrix<T> Matrix<T>::operator +(const Matrix<T> &m)
{
	if (_rowSize != m._rowSize || _colSize != m._colSize)
		cerr<<"Error: Matrix size is inconsistent for operator +!"<<endl;;

	Matrix<T> result(_rowSize, _colSize);

	for (int i=0; i<_rowSize; i++)
		for (int j=0; j<_colSize; j++)
			result.element[i][j] = element[i][j] + m.element[i][j];
	return result;
}

// Binary subtraction operator
template<typename T>
Matrix<T> Matrix<T>::operator -(const Matrix<T> &m)
{
	if (_rowSize != m._rowSize || _colSize != m._colSize)
		cerr<<"Error: Matrix size is inconsistent for operator -!"<<endl;

	Matrix<T> result(_rowSize, _colSize);

	for (int i=0; i<_rowSize; i++)
		for (int j=0; j<_colSize; j++)
			result.element[i][j] = element[i][j] - m.element[i][j];

	return result;
}

// Binary scalar multiplication operator
template<typename T>
Matrix<T> Matrix<T>::operator *(const T &num)
{
	Matrix<T> result(_rowSize, _colSize);

	for (int i=0; i<_rowSize; i++)
		for (int j=0; j<_colSize; j++)
			result.element[i][j] = element[i][j] * num;

	return result;
}

// Binary Matrix multiplication operator
template<typename T>
Matrix<T> Matrix<T>::operator *(const Matrix<T> &m)
{
	if (_colSize != m._rowSize)
	  cerr<<"Error: Matrix size is inconsistent for operator *!"<<endl;

	Matrix<T> result(_rowSize, m._colSize);

	for (int i=0; i < _rowSize; i++)
	  for (int j=0; j < m._colSize; j++)
	  {
		 result.element[i][j] = T(0);
		 for (int k=0; k < _colSize; k++)
			result.element[i][j] += element[i][k] * m.element[k][j];
	  }
	return result;
}

// Binary division operator
template<typename T>
Matrix<T> Matrix<T>::operator /(const T &num)
{
	Matrix<T> result(_rowSize, _colSize);

	for (int i=0; i<_rowSize; i++)
		for (int j=0; j<_colSize; j++)
			result.element[i][j] = element[i][j] / num;

	return result;
}

template<typename T>
int Matrix<T>::getRowSize()
{
	return _rowSize;
}

template<typename T>
int Matrix<T>::getColSize()
{
	return _colSize;
}

#endif
